// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

//go:build ignore

// This file generates exceptions.gen.go.
// It embeds the text of all the license LREs in the subdirectory "exceptions"
// and constructs the data structures to represent them.
// Run by a "go:generate" comment in licenses.go.

// Modified from github.com/google/licensecheck/gen_data.go.

package main

import (
	"bytes"
	"flag"
	"fmt"
	"go/format"
	"log"
	"os"
	"path/filepath"
	"sort"
	"strings"
	"text/template"

	"github.com/google/licensecheck"
)

func main() {
	log.SetFlags(0)
	log.SetPrefix("gen: ")
	flag.Parse()

	filesLRE, err := filepath.Glob(filepath.Join("exceptions", "*.lre"))
	if err != nil {
		log.Fatal(err)
	}
	if len(filesLRE) == 0 {
		log.Fatal("no license files")
	}

	code := outputTemplate
	out := new(bytes.Buffer)
	builtLRE := buildLRE(filesLRE)
	for _, file := range builtLRE {
		fmt.Fprintf(out, "\t\t{ID: %q, %s LRE: %v},\n", file.Name, file.Type, varName(file.Name+".lre"))
	}
	code = strings.Replace(code, "FILES_LIST", out.String(), -1)

	out.Reset()
	for _, file := range builtLRE {
		if len(file.Types) == 0 {
			continue
		}
		var ss []string
		for _, t := range file.Types {
			ss = append(ss, fmt.Sprintf("%q", t))
		}
		fmt.Fprintf(out, "\t\t%q: {%s},\n", file.Name, strings.Join(ss, ", "))
	}
	code = strings.Replace(code, "TYPES_LIST", out.String(), -1)

	out.Reset()
	for _, file := range builtLRE {
		fmt.Fprintf(out, "const %s = `%s`\n",
			varName(file.Name+".lre"),
			bytes.ReplaceAll(file.Data, []byte("`"), []byte("` + \"`\" + `")))
	}
	code += out.String()

	src, err := format.Source([]byte(code))
	if err != nil {
		fd, err1 := io.TempFile("", "license-data")
		if err1 == nil {
			_, err1 = fd.Write([]byte(code))
			if err1 == nil {
				log.Fatalf("parsing output (written to %s): %v", fd.Name(), err)
			}
			fd.Close()
		}
		log.Fatal("parsing output:", err)
	}
	err = os.WriteFile("exceptions.gen.go", src, 0644)
	if err != nil {
		log.Fatal(err)
	}
}

// varName returns the basename of the file, sanitized for use as a variable name,
// and given the prefix "license_".
func varName(file string) string {
	mapping := func(r rune) rune {
		if r == '-' || r == '.' || r == '+' {
			return '_'
		}
		return r
	}
	return "license_" + strings.Map(mapping, filepath.Base(file))
}

const outputTemplate = `
// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Code generated by gen_exceptions.go; DO NOT EDIT.

//lint:file-ignore ST1018 Ignore staticcheck message about Unicode control characters.

package licenses

import "github.com/google/licensecheck"

var exceptionLicenses = []licensecheck.License{
	FILES_LIST
}

var exceptionTypes = map[string][]string{
	TYPES_LIST
}
`

type fileData struct {
	Name  string
	Type  string   // licensecheck Type, not used
	Types []string // reported types
	Data  []byte
}

func buildLRE(filesLRE []string) []fileData {

	// setType obtains a licensecheck.Type from the template.
	// This is a bitset of license properties. Neither the licensecheck package
	// nor this package uses it.
	var typ licensecheck.Type
	setType := func(s string) (string, error) {
		t, err := licensecheck.ParseType(s)
		if err != nil {
			return "", err
		}
		typ = t
		return "", nil
	}

	// setTypes sets the reported license types for this package.
	var types []string
	setTypes := func(s string) string {
		types = strings.Fields(s)
		return ""
	}

	var out []fileData
	t := template.New("").Funcs(template.FuncMap{
		"list":  templateList,
		"Type":  setType,
		"Types": setTypes,
	})
	t, err := t.ParseFiles(filesLRE...)
	if err != nil {
		log.Fatal("parsing LRE templates:", err)
	}
	for _, t := range t.Templates() {
		if strings.HasSuffix(t.Name(), ".lre") {
			var buf bytes.Buffer
			typ = licensecheck.Unknown
			types = nil
			if err := t.Execute(&buf, nil); err != nil {
				log.Fatalf("executing %s: %v", t.Name(), err)
			}
			if len(bytes.TrimSpace(buf.Bytes())) == 0 {
				// Only contained useful definitions.
				continue
			}
			tstr := ""
			if typ != licensecheck.Unknown {
				tstr = "Type: " + typ.String() + ","
			}
			out = append(out, fileData{strings.TrimSuffix(t.Name(), ".lre"), tstr, types, buf.Bytes()})
		}
	}
	sort.Slice(out, func(i, j int) bool {
		ni, nj := out[i].Name, out[j].Name

		// Special case: BSD-4-Clause is a generalization of BSD-4-Clause-UC.
		// In case of multiple matches, licensecheck always returns the one earlier in the list.
		// Make BSD-4-Clause-UC the one earlier in the list.
		if strings.HasPrefix(ni, "BSD-4-Clause") && strings.HasPrefix(nj, "BSD-4-Clause") {
			ni, nj = nj, ni
		}

		// Special case: GFDL-1.[123]-invariants-* is a generalization of GFDL-1.[123]-no-invariants-*.
		// Reverse that order too.
		if strings.HasPrefix(ni, "GFDL-") && strings.HasPrefix(nj, "GFDL-") {
			ni, nj = nj, ni
		}

		return ni < nj
	})

	return out
}

// templateList returns xs, but it flattens any nested []any into the main list.
// Called from templates as "list", to pass multiple arguments to templates.
func templateList(xs ...any) []any {
	var list []any
	for _, x := range xs {
		switch x := x.(type) {
		case []any:
			list = append(list, x...)
		default:
			list = append(list, x)
		}
	}
	return list
}
